/**************************************************************************
 * @name   user_linked_list.c
 * @brief  这是一个大练习，创建用户链表
 * @date   2025/01/06
 * @anchor boluzs@163.com
 * @note
 *
 * @version 1.0.0
 *
 * 版权声明
 * CopyRight (c)   2019-2024   boluzs@163.com    All Right Reciver
 **************************************************************************/
#include "user_linked_list.h"
#include "printf_ui.h"
#include "main.h"

/**
 * @name: CircularDoubleLinkedList_Create
 * @brief: 创建带有头结点的空链表
 * @note: 双向循环链表是具有头结点，头结点类型和其他结点类型一致，但是头结点不存储有效数据
 * @retval: 函数调用成功，返回Node_p类型指针
 */
Node_p CircularDoubleLinkedList_Create(void)
{
    // 1.为头结点申请堆内存，并进行错误处理
    Node_p Pmanager = (Node_p)calloc(1, sizeof(Node_t));
    if (NULL == Pmanager)
    {
        fprintf(stderr, "Calloc For Node Error,errno=%d,%s\n", errno, strerror(errno));
        exit(-1);
    }

    // 2.初始化头结点指针域（数据域是空闲的）
    Pmanager->Prev = Pmanager;
    Pmanager->Next = Pmanager;
    printf("Create CircularDoubleLinkedList Successfully!\n");
    return Pmanager;
}

/**
 * @name: CircularDoubleLinkedList_NewNode
 * @brief: 创建新结点
 * @param {User_p} Data:待创建的结点数据域数据
 * @note: 新结点没有插入链表中，所以新结点指针域初值为NULL
 * @retval: 函数调用成功，返回Node_p类型指针
 */
Node_p CircularDoubleLinkedList_NewNode(User_p Data)
{
    // 1.为新结点申请堆内存，并进行错误处理
    Node_p NewNode = (Node_p)calloc(1, sizeof(Node_t));
    if (NULL == NewNode)
    {
        fprintf(stderr, "Calloc For Node Error,errno=%d,%s\n", errno, strerror(errno));
        exit(-1);
    }
    // 2.初始化新结点
    NewNode->Data = Data;
    NewNode->Next = NewNode;
    NewNode->Prev = NewNode;
    printf("Create NewNode Successfully!\n");
    return NewNode;
}

/**
 * @name: CircularDoubleLinkedList_HeadInsert
 * @brief: 利用头插法实现结点插入
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @param {DataType_t} Data:指的是待插入结点的数据域的值
 * @note: 头插法指的是把新结点作为链表的首结点
 * @retval: 成功返回true，失败返回false
 */
bool CircularDoubleLinkedList_HeadInsert(Node_p HeadPointer, User_p Data)
{
    // 为了提高程序的可靠性，所以需要对头指针进行备份
    Node_p p = HeadPointer;

    // 1.校验参数有效性
    if (NULL == HeadPointer)
    {
        fprintf(stderr, "Insert Error,Argument Invaild\n");
        return false;
    }

    // 2.创建新结点
    Node_p NewNode = CircularDoubleLinkedList_NewNode(Data);

    // 3.判断链表是否为空
    if (p == p->Next)
    {
        // 4.链表为空的情况
        p->Next = NewNode;
    }
    else
    {
        // 5.链表不为空的情况
        // 6.让首结点的前驱链接到新结点
        NewNode->Next = p->Next;
        // 7.让新结点的前驱指针域指向尾结点
        NewNode->Prev = p->Next->Prev;
        // 8.让首结点的前驱指针域指向新结点
        p->Next->Prev = NewNode;
        // 9.将尾结点的后继指针域指向新结点
        NewNode->Prev->Next = NewNode;
        // 10.将头结点的后继指针域指向新结点
        p->Next = NewNode;
    }
    return true;
}

/**
 * @name: CircularDoubleLinkedList_TailInsert
 * @brief: 利用尾插法实现结点插入
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @param {DataType_t} Data:指的是待插入结点的数据域的值
 * @note: 尾插法指的是把新结点作为链表的尾结点
 * @retval: 成功返回true，失败返回false
 */
bool CircularDoubleLinkedList_TailInsert(Node_p HeadPointer, User_p Data)
{
    // 为了提高程序的可靠性，所以需要对头指针进行备份
    Node_p p = HeadPointer;

    // 1.校验参数有效性
    if (NULL == HeadPointer)
    {
        fprintf(stderr, "Insert Error,Argument Invaild\n");
        return false;
    }

    // 2.创建新结点
    Node_p NewNode = CircularDoubleLinkedList_NewNode(Data);

    // 3.让新结点的prev指针域指向尾结点
    NewNode->Prev = p->Next->Prev;
    // 4.让尾结点的next指针域指向新结点
    p->Next->Prev->Next = NewNode;
    // 5.让首结点的prev指针域指向新结点
    p->Next->Prev = NewNode;
    // 6.让新结点的next指针域指向首结点
    NewNode->Next = p->Next;
    return true;
}

/**
 * @name: CircularDoubleLinkedList_HeadDel
 * @brief: 实现头删法
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @note: None
 * @retval: 成功返回true，失败返回false
 */
bool CircularDoubleLinkedList_HeadDel(Node_p HeadPointer)
{
    // 为了提高程序的可靠性，所以需要对头指针进行备份
    Node_p p = HeadPointer;

    // 1.校验参数有效性
    if (NULL == HeadPointer)
    {
        fprintf(stderr, "Delete Error,Argument Invaild\n");
        return false;
    }

    // 2.判断链表是否为空
    if (p->Next == p)
    {
        fprintf(stderr, "Delete Error,CircularDoubleLinkedList Is Empty\n");
        return false;
    }

    // 2.判断链表是否只有1个结点
    if (p->Next->Next == p->Next)
    {
        p->Next->Next = NULL;
        p->Next->Prev = NULL;
        free(p->Next);
        p->Next = HeadPointer;
    }
    else
    {
        // 3.让新的首结点的prev指针域指向尾结点
        p->Next->Next->Prev = p->Next->Prev;
        // 4.让头结点的next指向新的首结点
        p->Next = p->Next->Next;
        // 5.断掉旧的首结点的next和prev
        p->Next->Prev->Next->Next = NULL;
        p->Next->Prev->Next->Prev = NULL;
        // 6.释放旧的首结点
        free(p->Next->Prev->Next);
        // 7.让尾结点的next指向新的首结点
        p->Next->Prev->Next = p->Next;
    }
    printf("执行了一次头删法删除结点\n");
    return true;
}

/**
 * @name: CircularDoubleLinkedList_TailDel
 * @brief: 实现尾删法
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @note: None
 * @retval: 成功返回true，失败返回false
 */
bool CircularDoubleLinkedList_TailDel(Node_p HeadPointer)
{
    // 为了提高程序的可靠性，所以需要对头结点进行备份
    Node_p p = HeadPointer;

    // 1.校验参数有效性
    if (NULL == HeadPointer)
    {
        fprintf(stderr, "Delete Error,Argument Invaild\n");
        return false;
    }

    // 2.判断链表是否为空
    if (p->Next == p)
    {
        fprintf(stderr, "Delete Error,CircularDoubleLinkedList Is Empty\n");
        return false;
    }

    // 3.判断链表是否只有1个结点
    if (p->Next->Next == p->Next)
    {
        p->Next->Next = NULL;
        p->Next->Prev = NULL;
        free(p->Next);
        p->Next = HeadPointer;
    }
    else
    {
        // 4.让首结点的prev指针域指向尾结点的前驱
        p->Next->Prev = p->Next->Prev->Prev;
        // 5.断开尾结点的prev和next
        p->Next->Prev->Next->Prev = NULL;
        p->Next->Prev->Next->Next = NULL;
        // 6.释放尾结点
        free(p->Next->Prev->Next);
        // 7.让新的尾结点的next指向首结点
        p->Next->Prev->Next = p->Next;
    }
    printf("执行了一次尾删法删除结点\n");

    return true;
}

/**
 * @name: CircularDoubleLinkedList_SelectKData
 * @brief: 实现查找倒数第K个结点的Data
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @note: None
 * @retval: 成功返回1，失败返回0
 */
int CircularDoubleLinkedList_SelectKData(Node_p HeadPointer, int k)
{
    // 为了提高程序的可靠性，所以需要对头指针进行备份
    Node_p p = HeadPointer;

    // 1.校验参数有效性
    if (NULL == HeadPointer)
    {
        fprintf(stderr, "Select Error,Argument Invaild\n");
        return false;
    }

    // 2.判断链表是否为空
    if (p->Next == NULL)
    {
        fprintf(stderr, "Select Error,CircularDoubleLinkedList Is Empty\n");
        return false;
    }

    // 3.提前备份首结点地址
    Node_p temp = p;

    // 4.记录移动次数
    int cnt = 1;

    // 5.对双向循环链表进行遍历
    while (NULL != p->Next)
    {
        p = p->Next;
        cnt++;
        // 6.当p走到倒数第k个结点后，temp才开始移动
        if (cnt > k)
        {
            temp = temp->Next;
        }
    }

    // 7.如果temp==head，证明结点数量不够
    if (temp == HeadPointer)
    {
        return 0;
    }

    printf("当前链表倒数第%d个结点值是:%d\n", k, temp->Data->UserId);
    return 1;
}

/**
 * @name: CircularDoubleLinkedList_Print
 * @brief: 打印链表
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @note: None
 * @retval: 成功返回true，失败返回false
 */
bool CircularDoubleLinkedList_Print(Node_p HeadPointer)
{

    // 1.备份
    Node_p p = HeadPointer->Next;

    // 2.判断链表是否为空
    if (p == HeadPointer)
    {
        fprintf(stderr, "Print Error,CircularDoubleLinkedList Is Empty\n");
        return false;
    }
    printf("当前链表为:");
    // 2.遍历
    do
    {
        printf("%d ", p->Data->UserId);
        p = p->Next;
    } while (p != HeadPointer->Next);

    printf("\n");
    return true;
}

/**
 * @name: CircularDoubleLinkedList_DelMinNode
 * @brief: 删除链表最小值结点
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @note: None
 * @retval: 成功返回true，失败返回false
 */
bool CircularDoubleLinkedList_DelMinNode(Node_p HeadPointer)
{

    // 1.备份
    Node_p P = HeadPointer->Next;    // 用于记录当前结点
    Node_p Ptemp = HeadPointer;      // 用于记录当前结点的前驱结点
    Node_p Pmin = HeadPointer->Next; // 用于记录最小结点
    Node_p Pmin_temp = HeadPointer;  // 用于记录最小结点的前驱结点

    // 2.校验参数有效性
    if (NULL == HeadPointer)
    {
        fprintf(stderr, "Delete Error,Argument Invaild\n");
        return false;
    }

    // 3.判断链表是否为空
    if (Ptemp->Next == NULL)
    {
        fprintf(stderr, "Delete Error,CircularDoubleLinkedList Is Empty\n");
        return false;
    }
    // 4.遍历
    while (P != NULL)
    {
        // 5.跟最小的结点比较，比Pmin小则存储到Pmin中
        if (P->Data < Pmin->Data)
        {
            Pmin = P;
            Pmin_temp = Ptemp;
        }
        // 6.P和Ptemp继续移动
        P = P->Next;
        Ptemp = Ptemp->Next;
    }
    // 7.将Pmin_temp的指针域指向Pmin的指针域
    Pmin_temp->Next = Pmin->Next;
    Pmin->Next = NULL;
    free(Pmin);
    return true;
}

/**************************************************************************
 * @name: CircularDoubleLinkedList_ToSet
 * @brief: 递增非空单链表去重
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 * @note: None
 * @retval: 成功返回true，失败返回false
 **************************************************************************/
bool CircularDoubleLinkedList_ToSet(Node_p HeadPointer)
{
    // 1.用于记录是否有重复值域的结点
    Node_p p = HeadPointer->Next;
    // 2.用于记录需要删除的结点
    Node_p temp = HeadPointer->Next;

    // 3.遍历p是否等于temp
    while (p == temp)
    {
        // 4.相等则将temp往后移
        temp = temp->Next;
        // 5.比较p和temp的数据域是否相等
        while (temp->Data == p->Data)
        {
            // 6.判断temp是否尾结点
            if (temp->Next == NULL)
            {
                p->Next = NULL;
                free(temp);
            }
            else
            {
                // 7.删除temp结点
                p->Next = temp->Next;
                temp->Next = NULL;
                free(temp);
                // 8.将temp结点往后移动
                temp = p->Next;
            }
        }
        // 9.上面的循环跳出，则证明temp的结点的数据域不等于p的数据域，并且p不是尾结点，所以将p往后移动
        if (p->Next != NULL)
        {
            p = p->Next;
        }
    }
    return true;
}

/**************************************************************************
 * @name: User_Create
 * @brief: 注册新用户
 * @param {char *[7]} field:指针数组，用于存放字段信息
 *        {DataType_t} UserId:用户ID
 *        {char *} Name:用户名
 *        {char *} Phone:联系方式
 *        {float} Balance:用户余额
 *        {char *} CarNum:车牌号
 *        {char *} InTime:入场时间
 *        {char *} OutTime:出场时间
 * @note: None
 * @retval: 返回新用户的指针
 **************************************************************************/
User_p User_Create(char *field[7])
{
    // 1.为新用户申请堆内存，并进行错误处理
    User_p NewUser = (User_p)calloc(1, sizeof(User_t));
    if (NULL == NewUser)
    {
        fprintf(stderr, "Calloc For NewUser Error,errno=%d,%s\n", errno, strerror(errno));
        exit(-1);
    }

    // 2.初始化新用户
    NewUser->UserId = atoi(field[0]);
    strcpy(NewUser->Name, field[1]);
    strcpy(NewUser->Phone, field[2]);
    NewUser->Balance = atof(field[3]);
    strcpy(NewUser->CarNum, field[4]);
    strcpy(NewUser->InTime, field[5]);
    strcpy(NewUser->OutTime, field[6]);
    printf("Create NewUser Successfully!\n");
    return NewUser;
}

/**************************************************************************
 * @name: User_Create_ByTxt
 * @brief: 根据文件读取用户信息
 * @param {Node_p} HeadPointer:指的是指向头结点的头指针
 *        {const char *} pathname: 文件路径
 *        {const char *} Mode:打开文件模式
 * @note: None
 * @retval: 成功返回true，失败返回false
 **************************************************************************/
bool User_Create_ByTxt(Node_p HeadPointer, const char *PathName, const char *Mode)
{

    // 1.为了提高程序的可靠性，所以需要对头指针进行备份
    Node_p p = HeadPointer;
    char *User_Str = (char *)calloc(1, 128); // 记录文本中每一行的用户信息
    char *token;                             // 记录每个字段的信息
    const char s[2] = "|";                   // 记录用来切割的字符
    char *field[7] = {0};                    // 记录每一个字段
    int i = 0;                               // 记录每一个字段的下标

    // 2.打开文件
    FILE *fp = fopen(PathName, Mode);
    if (NULL == fp)
    {
        fprintf(stderr, "open error, errno= %d,%s!\n", errno, strerror(errno));
        return false;
    }

    // 3.读取每一行数据
    while (fgets(User_Str, 128, fp))
    {
        i = 0; // 重置下标
        /* 获取第一个字段 */
        token = strtok(User_Str, s);

        /* 继续获取其他的字段 */
        while (token != NULL)
        {
            // printf("%s\n", token);
            field[i] = token;
            i++;
            token = strtok(NULL, s);
        }
        // 4.将一行的信息用于创建用户
        User_p NewUser = User_Create(field);
        // 5.尾插法将用户插进链表中
        CircularDoubleLinkedList_TailInsert(p, NewUser);
    }

    free(User_Str);
    return true;
}

// 获取用户输入函数
void Get_User_Input(char *input, int length)
{
    fgets(input, length, stdin);
    input[strcspn(input, "\n")] = 0; // 去除换行符
}

// 用户添加信息
void User_Add()
{
    char user_info[256];
    char user_id[10], user_name[32], user_phone[15], user_balance[10], user_vehicle[15], start_time[20], end_time[20];

    PRINTF_UI_MsgAdd();

    printf("请输入用户ID: ");
    Get_User_Input(user_id, sizeof(user_id));

    printf("请输入用户名: ");
    Get_User_Input(user_name, sizeof(user_name));

    printf("请输入联系方式: ");
    Get_User_Input(user_phone, sizeof(user_phone));

    printf("请输入用户余额: ");
    Get_User_Input(user_balance, sizeof(user_balance));

    printf("请输入车牌号: ");
    Get_User_Input(user_vehicle, sizeof(user_vehicle));

    printf("请输入入场时间: ");
    Get_User_Input(start_time, sizeof(start_time));

    printf("请输入出场时间: ");
    Get_User_Input(end_time, sizeof(end_time));

    // 格式化用户信息
    snprintf(user_info, sizeof(user_info), "|%s|%s|%s|%s|%s|%s|%s|\n", user_id, user_name, user_phone, user_balance, user_vehicle, start_time, end_time);

    // 写入文件
    FILE *fp = fopen(USER_FILE_PATH, "a+b");
    if (fp != NULL)
    {
        fputs(user_info, fp);
        fclose(fp);
        printf("用户信息已添加。\n");
    }
    else
    {
        fprintf(stderr, "无法打开文件进行写入。\n");
    }
}

// 用户查询信息
void User_Query()
{
    char user_id[10];
    char line[256];
    int found = 0;

    printf("请输入要查询的用户ID: ");
    Get_User_Input(user_id, sizeof(user_id));

    FILE *fp = fopen(USER_FILE_PATH, "r+b");
    if (fp != NULL)
    {
        while (fgets(line, sizeof(line), fp))
        {
            if (strstr(line, user_id) != NULL)
            {
                printf("用户信息: %s", line);
                found = 1;
                break; // 找到第一个匹配项就退出
            }
        }
        fclose(fp);

        if (!found)
        {
            printf("无结果\n");
        }
    }
    else
    {
        fprintf(stderr, "无法打开文件进行查询。\n");
    }
}